Library Statements

library(ISLR)
library(dplyr)
library(readr)
library(broom)
library(ggplot2)
library(tidymodels) 
library(stringr)
library(splitstackshape)
library(lubridate)
library(rpart.plot)
library(cluster)
library(forcats)
tidymodels_prefer()
library(probably) #install.packages('probably')
library(vip)

Dataset

imdb_top_1000 <- read_csv("~/Desktop/Statistical Machine Learning/R Files/Final Project/imdb_top_1000_CLEAN.csv")

Data Cleaning

imdb_clean <- imdb_top_1000 %>%
  cSplit("Genre", sep = ",", direction = "wide") %>%
  mutate(Gross = log(Revenue-Budget)) %>%
  select(-...15)

runtime_clean <- imdb_top_1000$Runtime %>%
  str_replace(" min", "") %>%
  as.numeric()

imdb_clean$Runtime <- runtime_clean

imdb_clean <- imdb_clean %>%
  filter(Gross != "-Inf") %>%
  drop_na(Gross, Budget)

Data

head(imdb_clean)

Regression Models

Ordinary Linear Regression Model

Creation of CV Folds

data_cv10 <- vfold_cv(imdb_clean, v = 10)

Model Spec, Recipes, and Workflows for Linear Regression Model

# Model Spec

lm_spec <-
    linear_reg() %>% 
    set_engine(engine = 'lm') %>% 
    set_mode('regression')

# Recipe

full_lm_rec <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>% 
    step_normalize(all_numeric_predictors()) %>% 
    step_dummy(all_nominal_predictors()) %>%
    step_naomit(Gross, Runtime, IMDB_Rating, Meta_score, No_of_Votes)

# Workflow

full_lm_wf <- workflow() %>%
    add_recipe(full_lm_rec) %>%
    add_model(lm_spec)

Fit Full Model

full_lm_model <- fit(full_lm_wf, data = imdb_clean) 

full_lm_model %>% tidy()

Obtain Evaluation Metrics for Full Model

full_lm_modelcv <- fit_resamples(full_lm_wf, resamples = data_cv10, metrics = metric_set(rmse, rsq, mae))

full_lm_modelcv %>%
  collect_metrics()

Perform LASSO for Subset Selection

# Lasso Model Spec with tune

lm_lasso_spec_tune <- 
  linear_reg() %>%
  set_args(mixture = 1, penalty = tune()) %>%   # mixture = 1 indicates Lasso
  set_engine(engine = 'glmnet') %>%             
  set_mode('regression') 

# Recipe

data_rec_lasso <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>%                # removes variables with the same value (don't want duplicates)
    step_novel(all_nominal_predictors()) %>%      # important if you have rare categorical variables 
    step_normalize(all_numeric_predictors()) %>%  # standardization important step for LASSO
    step_dummy(all_nominal_predictors()) %>%      # creates indicator variables for categorical variables
    step_naomit(Gross, Runtime, IMDB_Rating,      # omit any NA values
                Meta_score, No_of_Votes)                            

# Workflow

lasso_wf_tune <- workflow() %>% 
  add_recipe(data_rec_lasso) %>%
  add_model(lm_lasso_spec_tune) 

# Tune Model (trying a variety of values of Lambda penalty)

penalty_grid <- grid_regular(
  penalty(range = c(-3, 1)),
  levels = 30)

tune_res <- tune_grid(
  lasso_wf_tune, 
  resamples = data_cv10, 
  metrics = metric_set(rmse, mae),
  grid = penalty_grid 
)

# Visualize Model Evaluation Metrics from Tuning

autoplot(tune_res) + theme_classic()

# Collect CV Metrics and Select Best Model

# Summarize Model Evaluation Metrics (CV)
lasso_mod <- collect_metrics(tune_res) %>%
  filter(.metric == 'rmse') %>%
  select(penalty, rmse = mean) 

# Choose penalty value
best_penalty <- select_best(tune_res, metric = 'rmse')

lasso_mod

Fit Final LASSO Model

# Fit Final Model

final_wf <- finalize_workflow(lasso_wf_tune, best_penalty) # incorporates penalty value to workflow

final_fit <- fit(final_wf, data = imdb_clean)

lasso_fit <- fit_resamples(final_wf, resamples = data_cv10, metrics = metric_set(rmse, rsq, mae))

tidy(final_fit)
# Final ("best") model predictors and coefficients

final_fit %>% tidy() %>% filter(estimate != 0)

Obtain Evaluation Metrics for Full Model

lasso_fit %>%
  collect_metrics()

Visualize Residuals for LASSO Model

lasso_mod_out <- final_fit %>%
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)

ggplot(lasso_mod_out, aes(x = .pred, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(lasso_mod_out, aes(x = Runtime, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(lasso_mod_out, aes(x = IMDB_Rating, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(lasso_mod_out, aes(x = No_of_Votes, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

GAM with Splines (TidyModels)

Build the GAM

# Build the GAM

gam_spec <- 
  gen_additive_mod() %>%
  set_engine(engine = 'mgcv') %>%
  set_mode('regression') 

gam_mod1 <- fit(gam_spec,
    Gross ~ s(Runtime) + s(IMDB_Rating) + Meta_score + s(No_of_Votes) + Genre_1,
    data = imdb_clean 
)

Run Diagnostics

# Diagnostics: Check to see if the number of knots is large enough (if p-value is low, increase number of knots)

gam_mod1 %>% pluck('fit') %>% mgcv::gam.check()

## 
## Method: GCV   Optimizer: magic
## Smoothing parameter selection converged after 4 iterations.
## The RMS GCV score gradient at convergence was 1.046117e-05 .
## The Hessian was positive definite.
## Model rank =  41 / 41 
## 
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
## 
##                  k'  edf k-index p-value  
## s(Runtime)     9.00 2.27    0.92    0.04 *
## s(IMDB_Rating) 9.00 4.08    0.97    0.21  
## s(No_of_Votes) 9.00 4.30    1.06    0.90  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Diagnostics: Check to see if the number of knots is large enough

gam_mod1 %>% pluck('fit') %>% summary() 
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## Gross ~ s(Runtime) + s(IMDB_Rating) + Meta_score + s(No_of_Votes) + 
##     Genre_1
## 
## Parametric coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      16.981283   0.430198  39.473  < 2e-16 ***
## Meta_score        0.009396   0.005328   1.763 0.078414 .  
## Genre_1Adventure -0.154761   0.247761  -0.625 0.532484    
## Genre_1Animation  1.157754   0.299124   3.870 0.000123 ***
## Genre_1Biography  0.134568   0.236486   0.569 0.569583    
## Genre_1Comedy     0.104267   0.226320   0.461 0.645206    
## Genre_1Crime     -0.671359   0.234979  -2.857 0.004448 ** 
## Genre_1Drama     -0.390133   0.191382  -2.039 0.042010 *  
## Genre_1Family    -0.509639   0.975559  -0.522 0.601611    
## Genre_1Film-Noir -2.833038   0.985900  -2.874 0.004226 ** 
## Genre_1Horror     0.406753   0.508980   0.799 0.424570    
## Genre_1Mystery   -1.162116   0.580799  -2.001 0.045928 *  
## Genre_1Thriller  -0.220396   1.373228  -0.160 0.872554    
## Genre_1Western   -0.527771   0.821316  -0.643 0.520775    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                  edf Ref.df     F p-value    
## s(Runtime)     2.270  2.909 20.14  <2e-16 ***
## s(IMDB_Rating) 4.076  5.024 17.84  <2e-16 ***
## s(No_of_Votes) 4.301  5.314 60.70  <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.504   Deviance explained = 52.5%
## GCV = 1.9256  Scale est. = 1.8377    n = 540

Visualization for Non-Linear Functions

# Visualize: Look at the estimated non-linear functions

gam_mod1 %>% pluck('fit') %>% plot()

Evaluation Metrics

gam1_output <- gam_mod1%>% 
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)

gam1_output %>%
    rmse(truth = Gross, estimate = .pred)
gam1_output %>%
    rsq(truth = Gross, estimate = .pred)

GAM with Splines (Recipe)

Build the GAM

spline_rec <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>% 
    step_normalize(all_numeric_predictors()) %>% 
    step_dummy(all_nominal_predictors()) %>%
    step_naomit(Gross) %>%
    step_ns(Runtime, deg_free = 2) %>%
    step_ns(No_of_Votes, deg_free = 6) %>%
    step_ns(IMDB_Rating, deg_free = 2)


spline_rec %>% prep(imdb_clean) %>% juice()
# Build the GAM

lm_spec_gam <-
  linear_reg() %>%
  set_engine(engine = 'lm') %>%
  set_mode('regression')

spline_wf <- workflow() %>%
    add_model(lm_spec) %>%
    add_recipe(spline_rec)

cv_output_spline2 <- fit_resamples( 
  spline_wf, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(mae,rmse,rsq)
)

Evaluation Metrics

cv_output_spline2 %>% collect_metrics()

idk what this is for

new_mod <- spline_wf %>%
  fit(data = imdb_clean)

new_mod %>%
  tidy() 
new_mod %>% 
  tidy() %>% 
  arrange(desc(abs(statistic)))
new_modcv <- fit_resamples(spline_wf, resamples = data_cv10, metrics = metric_set(mae, rmse, rsq))

GAM with Splines and LASSO

Build and Tune Model

# Lasso Model Spec with tune
gam_lasso_spec_tune <- 
  linear_reg() %>%
  set_args(mixture = 1, penalty = tune()) %>% ## mixture = 1 indicates Lasso
  set_engine(engine = 'glmnet') %>% 
  set_mode('regression') 

# Recipe with standardization (!) --> just include all these always
data_rec <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>% # removes variables with the same value (so duplicates don't mess up model)
    step_novel(all_nominal_predictors()) %>% # important if you have rare categorical variables 
    step_normalize(all_numeric_predictors()) %>%  # super important standardization step for LASSO
    step_dummy(all_nominal_predictors()) %>%  # creates indicator variables for categorical variables
    step_naomit(Gross)

# Workflow (Recipe + Model)
lasso_wf_tune1 <- workflow() %>% 
  add_recipe(data_rec) %>%
  add_model(gam_lasso_spec_tune) 

# Tune Model (trying a variety of values of Lambda penalty)
penalty_grid <- grid_regular(
  penalty(range = c(-3, 1)),
  levels = 30)

tune_res1 <- tune_grid( # new function for tuning parameters
  lasso_wf_tune1, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(rmse, mae),
  grid = penalty_grid # penalty grid defined above
)

# Visualize Model Evaluation Metrics from Tuning
autoplot(tune_res1) + theme_classic()

Collect CV Metrics and Select Best Model

# Summarize Model Evaluation Metrics (CV)
collect_metrics(tune_res) %>%
  filter(.metric == 'rmse') %>% # or choose mae
  select(penalty, rmse = mean) 
best_penalty1 <- select_best(tune_res1, metric = 'rmse') # choose penalty value based on lowest mae or rmse

Fit Final GAM LASSO Model

# Fit Final Model

final_wf_gam_lasso <- finalize_workflow(lasso_wf_tune1, best_penalty) # incorporates penalty value to workflow

final_fit_gam_lasso <- fit(final_wf_gam_lasso, data = imdb_clean)

tidy(final_fit_gam_lasso)
# Final ("best") model predictors and coefficients

final_fit_gam_lasso %>% tidy() %>% filter(estimate != 0)

Evaluation Metrics

gam_lasso_output <- fit_resamples( 
  final_wf_gam_lasso, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(mae,rmse,rsq)
)

gam_lasso_output %>% collect_metrics()

Compare GAM Models

gam1_output %>%
    rmse(truth = Gross, estimate = .pred)
cv_output_spline2 %>% collect_metrics()
gam_lasso_output %>% collect_metrics()

Visualize Residuals

# Visualize Residuals
gam1_output <- new_mod %>%
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)


ggplot(gam1_output, aes(x = .pred, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(gam1_output, aes(x = Runtime, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(gam1_output, aes(x = IMDB_Rating, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(gam1_output, aes(x = No_of_Votes, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

Compare All Linear Regression Model Performance

full_lm_modelcv %>% collect_metrics() #OLS
lasso_fit %>% collect_metrics() #LASSO
gam1_output %>%
    rmse(truth = Gross, estimate = .pred) #GAM

Bagging and Random Forests

imdb_clean_class <- imdb_clean %>%
  mutate(success_ratio = Revenue/Budget) %>%
  mutate(flop = as.factor(ifelse(success_ratio > 2, 'FALSE', 'TRUE'))) %>%
  drop_na(flop, No_of_Votes,Runtime, IMDB_Rating,Meta_score,Genre_1)
# Model Specification
rf_spec <- rand_forest() %>%
  set_engine(engine = 'ranger') %>% 
  set_args(trees = 1000, # Number of trees
           min_n = NULL,
           probability = FALSE,
           importance = 'impurity') %>%
  set_mode('classification')

# Recipe
data_rec <- recipe(flop ~ No_of_Votes + Runtime + IMDB_Rating + Meta_score + Genre_1,
                   data = imdb_clean_class) %>%
  step_naomit(flop, No_of_Votes, Runtime, IMDB_Rating, Meta_score, Genre_1)

# Create Workflows
data_wf_mtry3 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 3)) %>%
  add_recipe(data_rec) 

data_wf_mtry4 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 4)) %>%
  add_recipe(data_rec) 

data_wf_mtry5 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 5)) %>%
  add_recipe(data_rec)
# Fit Models
set.seed(123) # make sure to run this before each fit so that you have the same 1000 trees
data_fit_mtry3 <- fit(data_wf_mtry3, data = imdb_clean_class)

set.seed(123) 
data_fit_mtry4 <- fit(data_wf_mtry4, data = imdb_clean_class)

set.seed(123)
data_fit_mtry5 <- fit(data_wf_mtry5, data = imdb_clean_class)
# Custom Function to get OOB predictions, true observed outcomes and add a user-provided model label
rf_OOB_output <- function(fit_model, model_label, truth){
    tibble(
          .pred_class = fit_model %>% extract_fit_engine() %>% pluck('predictions'), #OOB predictions
          flop = truth,
          model = model_label
      )
}
# Evaluate OOB Metrics

data_rf_OOB_output <- bind_rows(
    rf_OOB_output(data_fit_mtry3,3, imdb_clean_class %>% pull(flop)),
    rf_OOB_output(data_fit_mtry4,4, imdb_clean_class %>% pull(flop)),
    rf_OOB_output(data_fit_mtry5,5, imdb_clean_class %>% pull(flop))
)


data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = flop, estimate = .pred_class)
data_rf_OOB_output %>% 
  group_by(model) %>%
  accuracy(truth = flop, estimate = .pred_class) %>%
  mutate(mtry = as.numeric(stringr::str_replace(model,'mtry',''))) %>%
  ggplot(aes(x = mtry, y = .estimate )) + 
  geom_point() +
  geom_line() +
  theme_classic()

Evaluating the Forest

rf_OOB_output(data_fit_mtry4,4, imdb_clean_class %>% pull(flop)) %>%
    conf_mat(truth = flop, estimate= .pred_class)
##           Truth
## Prediction FALSE TRUE
##      FALSE   468   65
##      TRUE      7    0

Variable Importance

# Impurity

model_output <-data_fit_mtry4 %>% 
    extract_fit_engine() 

model_output %>% 
    vip(num_features = 10) + theme_classic() #based on impurity, 10 meaning the top 10

model_output %>% vip::vi() %>% head()
model_output %>% vip::vi() %>% tail()
# Permutation

model_output2 <- data_wf_mtry4 %>% 
  update_model(rf_spec %>% set_args(importance = "permutation")) %>% #based on permutation
  fit(data = imdb_clean_class) %>% 
    extract_fit_engine() 

model_output2 %>% 
    vip(num_features = 10) + theme_classic()

model_output2 %>% vip::vi() %>% head()
model_output2 %>% vip::vi() %>% tail()
ggplot(imdb_clean_class, aes(x = flop, y = No_of_Votes)) +
    geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = Runtime)) +
    geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = IMDB_Rating)) +
    geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = Meta_score)) +
    geom_violin() + theme_classic()

Logistic Regression

set.seed(123)

# Logistic Regression Model Spec
logistic_spec <- logistic_reg() %>%
    set_engine('glm') %>%
    set_mode('classification')

# Recipe
logistic_rec <- recipe(flop ~ No_of_Votes + Runtime + IMDB_Rating + Genre_1,
                   data = imdb_clean_class)

# Workflow (Recipe + Model) for Full Log Model
log_wf <- workflow() %>%
    add_recipe(logistic_rec) %>%
    add_model(logistic_spec)

# Fit Model
log_fit <- fit(log_wf, data = imdb_clean_class)

tidy(log_fit)
log_fit %>% tidy() %>%
  mutate(OR = exp(estimate))

Ask Bryan about relevel factor… do we need it?

# Creation of CV Folds
data_cv10_class <- vfold_cv(imdb_clean_class, v = 10)

Recipes and Workflows

full_log_wf <- workflow() %>%
  add_recipe(logistic_rec) %>%
  add_model(logistic_spec)
    
log_model <- fit(full_log_wf, data = imdb_clean_class) 

log_modelcv <- fit_resamples(full_log_wf, resamples = data_cv10_class, metrics = metric_set(accuracy,sens,yardstick::spec))

log_modelcv %>%
  collect_metrics()
# Soft Predictions on Training Data
final_output <- log_fit %>% predict(new_data = imdb_clean_class, type='prob') %>% bind_cols(imdb_clean_class)

final_output %>%
  ggplot(aes(x = flop, y = .pred_TRUE)) +
  geom_boxplot()

# Use soft predictions
final_output %>%
    roc_curve(flop,.pred_TRUE,event_level = 'second') %>%
    autoplot()

# thresholds in terms of reference level
threshold_output <- final_output %>%
    threshold_perf(truth = flop, estimate = .pred_FALSE, thresholds = seq(0,1,by=.01)) 

# J-index v. threshold for no flop
threshold_output %>%
    filter(.metric == 'j_index') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'J-index', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'j_index') %>%
    arrange(desc(.estimate))
# Distance v. threshold for not_spam

threshold_output %>%
    filter(.metric == 'distance') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'Distance', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'distance') %>%
    arrange(.estimate)
log_metrics <- metric_set(accuracy,sens,yardstick::spec)

final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = .12)) %>%
    log_metrics(truth = flop, estimate = .pred_class, event_level = 'second')
final_output %>%
  mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = .12)) %>%
  conf_mat(truth = flop, estimate = .pred_class)
##           Truth
## Prediction FALSE TRUE
##      FALSE   475   65
##      TRUE      0    0
predict(log_fit, new_data = data.frame(No_of_Votes = 10000, Runtime = 112, IMDB_Rating = 9.8,
                                        Genre_1 = "Drama"), type = "prob"
)
predict(log_fit, new_data = data.frame(No_of_Votes = 10000, Runtime = 112, IMDB_Rating = 9.8,
                                        Genre_1 = "Drama"), type = "class"
)

Must manually calculate hard prediction

K-Means Clustering

ggplot(imdb_clean, aes(x = Budget, y = Runtime)) + 
  geom_point() + theme_classic()

ggplot(imdb_clean, aes(x = Budget, y = Gross)) + 
  geom_point() + theme_classic()

ggplot(imdb_clean, aes(x = No_of_Votes, y = Runtime)) + 
  geom_point() + theme_classic()

ggplot(imdb_clean, aes(x = Gross, y = No_of_Votes)) + 
  geom_point() + theme_classic()

# Select just the bill length and depth variables
imdb_sub <- imdb_clean %>%
    select(Budget, Runtime)

# Run k-means for k = centers = 3
set.seed(253)
kclust_k10 <- kmeans(scale(imdb_sub), centers = 10)

# Display the cluter assignments
kclust_k10$cluster
##   [1]  7  9  3 10  5 10  9 10  8  4  8  7  9  8  4  2  8  2  4 10  1  7  2  2  2
##  [26]  1  7  4  9  8  2  1  1  5  4  9  1  1  1  5  8  3  9  9  3  1  1  9  9  1
##  [51]  5  1  1  2  1  2  4  1  1  1  2  2  5 10  5  1  2  9  9  9  1  2  2  9  2
##  [76] 10  2  2  2  1  5  1  2  5  9 10  2  4  3  8  9  9  3  7  9  2  8  9  7  1
## [101]  7  9 10  2  7  7  2  5  9  2 10  2  2  1  2  5  2  2  9  1  4  7  2  2  5
## [126]  2  9  3  1  4  5  5  3  9  2  7  2  5 10  1  6  7  9  6  1  6  1  5  5 10
## [151]  1  7  2  2  5  2  9  1  5 10  1  2  5 10  2  2 10  2  2  2  2 10  1  9  1
## [176]  1  2  2 10  1  5  5  2  9  7  2  4  3  5  1  3  2  1  2  9  6  9  2  1  1
## [201]  7  4  6  7  1  3  9  7  2  4  8  4  6 10 10  2  1  2  2  7  5  5  1  9  5
## [226] 10  5 10  2  7  5  2  1 10  2  5  2  2  1  9  2  2 10  1  2  1  1  2  9 10
## [251]  5  2  9  2  5  1  2  2  5  5  9  1  1  5  1  2  1  1  7  1  3  1  3  2  5
## [276]  1  1  1  2  9  3  1  7  2  4  5  3  5  2  7  8  1  7  6  9  1  7  1  1  2
## [301]  1  5  5  7  5  2  7  2  9  5  1  1  4 10 10  2  5  1  5  1  1  1  1  7  6
## [326]  2  3  9  3  2  2  2  5  6  7  4  7  9  3  9  1  5  5  5  3  2  7  1  8  3
## [351]  7  1  2  8 10  7  7  7  2  7  5  1  1  4  6  8  7  9  7  1  2  7  5  1  1
## [376]  2  2  1  2  1  5  1  1  9  5  2  1  1  1  2  1  1  1  1  1  1  9  2 10  2
## [401]  1  5  5  1  1  1  5  4  2  1  3  9  1  3  5  5  6  6  3  2  7  1  3  2  7
## [426]  5  3  7  1  1  2  4  1  3  9  2  6  1  9  1  8  4  6  8  1  7  1  4  6  1
## [451]  1  5  7  5  1  7  4  7  7  5 10  7  5 10  1  7  2  6  9  2  5  5  2  1  5
## [476]  5  5  5  5  2  1  1  1  1  9  1  9  2  2  1  2  1  2  1  2  1  2  3  1  7
## [501]  7  7  1  5  3  1  1  1  6  1  1  1  7  5  1  4  7  1  7  6  4  7  1  1  3
## [526]  6  2  8  1  1  5  2  1  1  1  1  7  4  4  7  1  5  9  6  1  7  2  4  7  7
## [551]  1  1  2  1  9  5  5  5  1  1  5  5  5  1  5  2  7  9  5  1 10  2
# Add a variable (kclust_3) to the original dataset 
# containing the cluster assignments
imdb_clean <- imdb_clean %>%
    mutate(kclust_10 = factor(kclust_k10$cluster))
# Visualize the cluster assignments on the original scatterplot
imdb_clean %>%
  ggplot(aes(x = Budget, y = Runtime, color = kclust_10)) +
    geom_point() + theme_classic()

# Data-specific function to cluster and calculate total within-cluster SS
imdb_cluster_ss <- function(k){
    # Perform clustering
    kclust <- kmeans(scale(imdb_sub), centers = k)

    # Return the total within-cluster sum of squares
    return(kclust$tot.withinss)
}

tibble(
    k = 1:15,
    tot_wc_ss = purrr::map_dbl(1:15, imdb_cluster_ss)
) %>% 
    ggplot(aes(x = k, y = tot_wc_ss)) +
    geom_point() + 
    labs(x = "Number of clusters",y = 'Total within-cluster sum of squares') + 
    theme_classic()

kclust_k8 <- kmeans(scale(imdb_sub), centers = 8)

# Display the cluter assignments
kclust_k8$cluster
##   [1] 5 2 8 2 1 2 5 2 8 7 6 5 5 6 7 3 6 3 6 2 4 3 3 3 3 4 3 6 5 6 3 4 4 1 6 5 4
##  [38] 4 4 1 6 8 5 5 8 4 4 5 5 4 1 4 4 3 4 3 6 4 4 4 3 3 1 6 1 4 3 5 5 5 4 3 3 5
##  [75] 3 2 3 3 3 4 1 4 3 1 5 2 3 6 8 6 5 5 8 7 5 3 8 5 7 4 3 5 2 3 7 3 3 1 2 3 2
## [112] 3 4 4 3 1 4 3 5 4 5 5 3 3 1 3 5 8 4 6 1 1 8 5 3 3 3 1 2 4 7 5 5 7 7 7 4 1
## [149] 1 2 4 7 3 3 1 3 5 4 1 2 4 3 1 2 3 3 2 3 3 3 3 2 4 5 4 4 3 3 2 4 1 1 3 5 3
## [186] 4 6 8 1 4 8 3 4 4 5 7 5 3 4 4 7 6 7 3 4 8 5 3 3 6 6 7 7 2 2 3 4 3 3 3 1 1
## [223] 4 5 1 2 1 2 3 3 1 3 4 2 3 1 3 3 4 5 3 4 2 4 3 4 4 3 2 2 1 3 2 3 1 4 4 3 1
## [260] 1 5 4 4 1 4 4 4 4 7 4 8 4 8 3 1 4 4 4 3 5 8 4 5 4 6 1 8 1 3 3 6 4 7 7 5 4
## [297] 5 4 4 3 4 1 1 3 1 3 3 3 2 1 4 4 7 2 2 4 1 4 1 4 4 4 4 3 7 3 8 5 8 3 3 3 1
## [334] 8 3 6 7 5 8 5 4 1 1 1 8 3 3 4 6 8 5 4 3 6 2 3 3 3 3 3 1 4 4 6 7 8 7 5 3 4
## [371] 3 3 1 4 4 3 3 7 3 4 1 4 4 5 1 3 4 4 4 3 4 4 4 4 4 4 5 3 2 3 4 1 1 4 4 4 1
## [408] 7 3 4 8 5 4 8 1 1 7 7 8 3 7 4 8 3 7 1 8 5 4 4 3 6 4 8 5 3 7 4 5 4 6 6 7 6
## [445] 4 3 4 6 7 4 4 1 7 1 4 5 7 3 3 1 2 3 1 2 7 5 3 7 5 3 1 1 3 4 1 1 1 1 1 3 4
## [482] 4 4 4 5 4 5 3 3 4 3 4 3 4 3 4 3 8 4 5 3 3 4 1 8 4 4 4 7 4 4 4 7 1 4 7 3 4
## [519] 7 7 7 7 4 7 8 7 3 6 4 4 1 3 4 4 4 4 7 7 6 5 4 1 5 7 4 5 3 7 3 5 4 4 3 4 5
## [556] 1 1 1 4 4 1 1 1 4 1 3 3 5 1 4 2 4
# Add a variable (kclust_3) to the original dataset 
# containing the cluster assignments
imdb_clean <- imdb_clean %>%
    mutate(kclust_8 = factor(kclust_k8$cluster))
# Visualize the cluster assignments on the original scatterplot
imdb_clean %>%
  ggplot(aes(x = Budget, y = Runtime, color = kclust_8)) +
    geom_point() + theme_classic()

Pick either k = 8 or k = 10 –> there are 7 genres with larger n and 13 total genres. Film noir is fancy way of saying crime drama (ex. Knives Out). One thing to note is a significant number of movies have Genre_2 listed as Family Mystery and Horror (and many have one of the main genres listed as genre 2 as well). What is a good way to summarize both Genre 1 and 2?

imdb_clean %>%
  count(Genre_1)
imdb_clean %>%
  count(Genre_2)

Interpreting the Clusters

imdb_clean %>%
  group_by(kclust_10) %>%
  summarize(across(c(Gross, Budget), mean))
imdb_clean %>%
  group_by(kclust_8) %>%
  count(Genre_1)
imdb_clean %>%
  group_by(kclust_8) %>%
  count(Genre_2)
imdb_clean%>%
  count(kclust_8)

Hierarchial Clustering

# Random subsample of 50 penguins
set.seed(253)
imdb_hc <- imdb_clean %>%
    slice_sample(n = 25)

# Select the variables to be used in clustering
imdb_hc_sub <- imdb_hc %>%
    select(Gross, Budget)

imdb_hc_full <- imdb_clean %>%
  select(Gross, Budget)

# Summary statistics for the variables
summary(imdb_hc_sub)
##      Gross           Budget         
##  Min.   :13.82   Min.   :        0  
##  1st Qu.:15.98   1st Qu.:  3000000  
##  Median :17.54   Median : 18500000  
##  Mean   :17.34   Mean   : 39318800  
##  3rd Qu.:18.88   3rd Qu.: 60000000  
##  Max.   :20.31   Max.   :190000000
# Compute a distance matrix on the scaled data
dist_mat_scaled <- dist(scale(imdb_hc_sub))

dist_mat_full <- dist(scale(imdb_hc_full))
imdb_hc_avg <- hclust(dist_mat_scaled, method = "average")
imdb_full_avg <- hclust(dist_mat_full, method = "average")

# Plot dendrogram
plot(imdb_hc_avg)

plot(imdb_hc_avg, labels = imdb_hc$Genre_1)

plot(imdb_hc_avg, labels = paste(imdb_hc$Genre_1, imdb_hc$Genre_2))

imdb_clean <- imdb_clean %>%
    mutate(
        hclust_num = factor(cutree(imdb_full_avg, k = 3)) # Cut into 6 clusters (k)
    )
ggplot(imdb_clean, aes(x = hclust_num, fill = Genre_1)) +
    geom_bar(position = "fill") +
    labs(x = "Cluster") + 
    theme_classic()

LS0tCnRpdGxlOiAiRmluYWwgUHJvamVjdCBDb2RlIEFMTCIKYXV0aG9yOiAiRW1pbHksIEp1bGlhbiwgSmFjb2IsIGFuZCBBcmlzdG8iCmRhdGU6ICcyMDIyLTExLTIwJwpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IHBhcGVyCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXJyb3I9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkKYGBgCgojIExpYnJhcnkgU3RhdGVtZW50cyAKCmBgYHtyfQpsaWJyYXJ5KElTTFIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh0aWR5bW9kZWxzKSAKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHNwbGl0c3RhY2tzaGFwZSkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KGZvcmNhdHMpCnRpZHltb2RlbHNfcHJlZmVyKCkKbGlicmFyeShwcm9iYWJseSkgI2luc3RhbGwucGFja2FnZXMoJ3Byb2JhYmx5JykKbGlicmFyeSh2aXApCmBgYAoKIyBEYXRhc2V0CgpgYGB7cn0KaW1kYl90b3BfMTAwMCA8LSByZWFkX2Nzdigifi9EZXNrdG9wL1N0YXRpc3RpY2FsIE1hY2hpbmUgTGVhcm5pbmcvUiBGaWxlcy9GaW5hbCBQcm9qZWN0L2ltZGJfdG9wXzEwMDBfQ0xFQU4uY3N2IikKYGBgCgojIyBEYXRhIENsZWFuaW5nCgpgYGB7cn0KaW1kYl9jbGVhbiA8LSBpbWRiX3RvcF8xMDAwICU+JQogIGNTcGxpdCgiR2VucmUiLCBzZXAgPSAiLCIsIGRpcmVjdGlvbiA9ICJ3aWRlIikgJT4lCiAgbXV0YXRlKEdyb3NzID0gbG9nKFJldmVudWUtQnVkZ2V0KSkgJT4lCiAgc2VsZWN0KC0uLi4xNSkKCnJ1bnRpbWVfY2xlYW4gPC0gaW1kYl90b3BfMTAwMCRSdW50aW1lICU+JQogIHN0cl9yZXBsYWNlKCIgbWluIiwgIiIpICU+JQogIGFzLm51bWVyaWMoKQoKaW1kYl9jbGVhbiRSdW50aW1lIDwtIHJ1bnRpbWVfY2xlYW4KCmltZGJfY2xlYW4gPC0gaW1kYl9jbGVhbiAlPiUKICBmaWx0ZXIoR3Jvc3MgIT0gIi1JbmYiKSAlPiUKICBkcm9wX25hKEdyb3NzLCBCdWRnZXQpCmBgYAoKIyMgRGF0YQoKYGBge3J9CmhlYWQoaW1kYl9jbGVhbikKYGBgCgojIFJlZ3Jlc3Npb24gTW9kZWxzCgojIyBPcmRpbmFyeSBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbAoKIyMjIENyZWF0aW9uIG9mIENWIEZvbGRzCgpgYGB7cn0KZGF0YV9jdjEwIDwtIHZmb2xkX2N2KGltZGJfY2xlYW4sIHYgPSAxMCkKYGBgCgojIyMgTW9kZWwgU3BlYywgUmVjaXBlcywgYW5kIFdvcmtmbG93cyBmb3IgTGluZWFyIFJlZ3Jlc3Npb24gTW9kZWwKCmBgYHtyfQojIE1vZGVsIFNwZWMKCmxtX3NwZWMgPC0KICAgIGxpbmVhcl9yZWcoKSAlPiUgCiAgICBzZXRfZW5naW5lKGVuZ2luZSA9ICdsbScpICU+JSAKICAgIHNldF9tb2RlKCdyZWdyZXNzaW9uJykKCiMgUmVjaXBlCgpmdWxsX2xtX3JlYyA8LSByZWNpcGUoR3Jvc3MgfiBSdW50aW1lICsgSU1EQl9SYXRpbmcgKyBNZXRhX3Njb3JlICsgCiAgICAgICAgICAgICAgICAgICBOb19vZl9Wb3RlcyArIEdlbnJlXzEsIGRhdGEgPSBpbWRiX2NsZWFuKSAlPiUKICAgIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCkpICU+JSAKICAgIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lIAogICAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JQogICAgc3RlcF9uYW9taXQoR3Jvc3MsIFJ1bnRpbWUsIElNREJfUmF0aW5nLCBNZXRhX3Njb3JlLCBOb19vZl9Wb3RlcykKCiMgV29ya2Zsb3cKCmZ1bGxfbG1fd2YgPC0gd29ya2Zsb3coKSAlPiUKICAgIGFkZF9yZWNpcGUoZnVsbF9sbV9yZWMpICU+JQogICAgYWRkX21vZGVsKGxtX3NwZWMpCmBgYAoKIyMjIEZpdCBGdWxsIE1vZGVsCgpgYGB7cn0KZnVsbF9sbV9tb2RlbCA8LSBmaXQoZnVsbF9sbV93ZiwgZGF0YSA9IGltZGJfY2xlYW4pIAoKZnVsbF9sbV9tb2RlbCAlPiUgdGlkeSgpCmBgYAoKIyMjIE9idGFpbiBFdmFsdWF0aW9uIE1ldHJpY3MgZm9yIEZ1bGwgTW9kZWwKCmBgYHtyfQpmdWxsX2xtX21vZGVsY3YgPC0gZml0X3Jlc2FtcGxlcyhmdWxsX2xtX3dmLCByZXNhbXBsZXMgPSBkYXRhX2N2MTAsIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIHJzcSwgbWFlKSkKCmZ1bGxfbG1fbW9kZWxjdiAlPiUKICBjb2xsZWN0X21ldHJpY3MoKQpgYGAKCiMjIFBlcmZvcm0gTEFTU08gZm9yIFN1YnNldCBTZWxlY3Rpb24KCmBgYHtyfQojIExhc3NvIE1vZGVsIFNwZWMgd2l0aCB0dW5lCgpsbV9sYXNzb19zcGVjX3R1bmUgPC0gCiAgbGluZWFyX3JlZygpICU+JQogIHNldF9hcmdzKG1peHR1cmUgPSAxLCBwZW5hbHR5ID0gdHVuZSgpKSAlPiUgICAjIG1peHR1cmUgPSAxIGluZGljYXRlcyBMYXNzbwogIHNldF9lbmdpbmUoZW5naW5lID0gJ2dsbW5ldCcpICU+JSAgICAgICAgICAgICAKICBzZXRfbW9kZSgncmVncmVzc2lvbicpIAoKIyBSZWNpcGUKCmRhdGFfcmVjX2xhc3NvIDwtIHJlY2lwZShHcm9zcyB+IFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyAKICAgICAgICAgICAgICAgICAgIE5vX29mX1ZvdGVzICsgR2VucmVfMSwgZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lICAgICAgICAgICAgICAgICMgcmVtb3ZlcyB2YXJpYWJsZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZSAoZG9uJ3Qgd2FudCBkdXBsaWNhdGVzKQogICAgc3RlcF9ub3ZlbChhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSAgICAgICMgaW1wb3J0YW50IGlmIHlvdSBoYXZlIHJhcmUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIAogICAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgICMgc3RhbmRhcmRpemF0aW9uIGltcG9ydGFudCBzdGVwIGZvciBMQVNTTwogICAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSAgICAgICMgY3JlYXRlcyBpbmRpY2F0b3IgdmFyaWFibGVzIGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKICAgIHN0ZXBfbmFvbWl0KEdyb3NzLCBSdW50aW1lLCBJTURCX1JhdGluZywgICAgICAjIG9taXQgYW55IE5BIHZhbHVlcwogICAgICAgICAgICAgICAgTWV0YV9zY29yZSwgTm9fb2ZfVm90ZXMpICAgICAgICAgICAgICAgICAgICAgICAgICAgIAoKIyBXb3JrZmxvdwoKbGFzc29fd2ZfdHVuZSA8LSB3b3JrZmxvdygpICU+JSAKICBhZGRfcmVjaXBlKGRhdGFfcmVjX2xhc3NvKSAlPiUKICBhZGRfbW9kZWwobG1fbGFzc29fc3BlY190dW5lKSAKCiMgVHVuZSBNb2RlbCAodHJ5aW5nIGEgdmFyaWV0eSBvZiB2YWx1ZXMgb2YgTGFtYmRhIHBlbmFsdHkpCgpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKAogIHBlbmFsdHkocmFuZ2UgPSBjKC0zLCAxKSksCiAgbGV2ZWxzID0gMzApCgp0dW5lX3JlcyA8LSB0dW5lX2dyaWQoCiAgbGFzc29fd2ZfdHVuZSwgCiAgcmVzYW1wbGVzID0gZGF0YV9jdjEwLCAKICBtZXRyaWNzID0gbWV0cmljX3NldChybXNlLCBtYWUpLAogIGdyaWQgPSBwZW5hbHR5X2dyaWQgCikKCiMgVmlzdWFsaXplIE1vZGVsIEV2YWx1YXRpb24gTWV0cmljcyBmcm9tIFR1bmluZwoKYXV0b3Bsb3QodHVuZV9yZXMpICsgdGhlbWVfY2xhc3NpYygpCgojIENvbGxlY3QgQ1YgTWV0cmljcyBhbmQgU2VsZWN0IEJlc3QgTW9kZWwKCiMgU3VtbWFyaXplIE1vZGVsIEV2YWx1YXRpb24gTWV0cmljcyAoQ1YpCmxhc3NvX21vZCA8LSBjb2xsZWN0X21ldHJpY3ModHVuZV9yZXMpICU+JQogIGZpbHRlcigubWV0cmljID09ICdybXNlJykgJT4lCiAgc2VsZWN0KHBlbmFsdHksIHJtc2UgPSBtZWFuKSAKCiMgQ2hvb3NlIHBlbmFsdHkgdmFsdWUKYmVzdF9wZW5hbHR5IDwtIHNlbGVjdF9iZXN0KHR1bmVfcmVzLCBtZXRyaWMgPSAncm1zZScpCgpsYXNzb19tb2QKYGBgCgojIyMgRml0IEZpbmFsIExBU1NPIE1vZGVsCgpgYGB7cn0KIyBGaXQgRmluYWwgTW9kZWwKCmZpbmFsX3dmIDwtIGZpbmFsaXplX3dvcmtmbG93KGxhc3NvX3dmX3R1bmUsIGJlc3RfcGVuYWx0eSkgIyBpbmNvcnBvcmF0ZXMgcGVuYWx0eSB2YWx1ZSB0byB3b3JrZmxvdwoKZmluYWxfZml0IDwtIGZpdChmaW5hbF93ZiwgZGF0YSA9IGltZGJfY2xlYW4pCgpsYXNzb19maXQgPC0gZml0X3Jlc2FtcGxlcyhmaW5hbF93ZiwgcmVzYW1wbGVzID0gZGF0YV9jdjEwLCBtZXRyaWNzID0gbWV0cmljX3NldChybXNlLCByc3EsIG1hZSkpCgp0aWR5KGZpbmFsX2ZpdCkKCiMgRmluYWwgKCJiZXN0IikgbW9kZWwgcHJlZGljdG9ycyBhbmQgY29lZmZpY2llbnRzCgpmaW5hbF9maXQgJT4lIHRpZHkoKSAlPiUgZmlsdGVyKGVzdGltYXRlICE9IDApCmBgYAoKIyMjIE9idGFpbiBFdmFsdWF0aW9uIE1ldHJpY3MgZm9yIEZ1bGwgTW9kZWwKCmBgYHtyfQpsYXNzb19maXQgJT4lCiAgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgojIyMgVmlzdWFsaXplIFJlc2lkdWFscyBmb3IgTEFTU08gTW9kZWwKCmBgYHtyfQpsYXNzb19tb2Rfb3V0IDwtIGZpbmFsX2ZpdCAlPiUKICAgIHByZWRpY3QobmV3X2RhdGEgPSBpbWRiX2NsZWFuKSAlPiUKICAgIGJpbmRfY29scyhpbWRiX2NsZWFuKSAlPiUKICAgIG11dGF0ZShyZXNpZCA9IEdyb3NzIC0gLnByZWQpCgpnZ3Bsb3QobGFzc29fbW9kX291dCwgYWVzKHggPSAucHJlZCwgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGxhc3NvX21vZF9vdXQsIGFlcyh4ID0gUnVudGltZSwgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGxhc3NvX21vZF9vdXQsIGFlcyh4ID0gSU1EQl9SYXRpbmcsIHkgPSByZXNpZCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChsYXNzb19tb2Rfb3V0LCBhZXMoeCA9IE5vX29mX1ZvdGVzLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMgR0FNIHdpdGggU3BsaW5lcyAoVGlkeU1vZGVscykKCiMjIyBCdWlsZCB0aGUgR0FNCgpgYGB7cn0KIyBCdWlsZCB0aGUgR0FNCgpnYW1fc3BlYyA8LSAKICBnZW5fYWRkaXRpdmVfbW9kKCkgJT4lCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAnbWdjdicpICU+JQogIHNldF9tb2RlKCdyZWdyZXNzaW9uJykgCgpnYW1fbW9kMSA8LSBmaXQoZ2FtX3NwZWMsCiAgICBHcm9zcyB+IHMoUnVudGltZSkgKyBzKElNREJfUmF0aW5nKSArIE1ldGFfc2NvcmUgKyBzKE5vX29mX1ZvdGVzKSArIEdlbnJlXzEsCiAgICBkYXRhID0gaW1kYl9jbGVhbiAKKQoKYGBgCgojIyMgUnVuIERpYWdub3N0aWNzCgpgYGBge3J9CiMgRGlhZ25vc3RpY3M6IENoZWNrIHRvIHNlZSBpZiB0aGUgbnVtYmVyIG9mIGtub3RzIGlzIGxhcmdlIGVub3VnaCAoaWYgcC12YWx1ZSBpcyBsb3csIGluY3JlYXNlIG51bWJlciBvZiBrbm90cykKCmdhbV9tb2QxICU+JSBwbHVjaygnZml0JykgJT4lIG1nY3Y6OmdhbS5jaGVjaygpCmBgYAoKYGBge3J9CiMgRGlhZ25vc3RpY3M6IENoZWNrIHRvIHNlZSBpZiB0aGUgbnVtYmVyIG9mIGtub3RzIGlzIGxhcmdlIGVub3VnaAoKZ2FtX21vZDEgJT4lIHBsdWNrKCdmaXQnKSAlPiUgc3VtbWFyeSgpIApgYGAKCiMjIyBWaXN1YWxpemF0aW9uIGZvciBOb24tTGluZWFyIEZ1bmN0aW9ucwoKYGBge3J9CiMgVmlzdWFsaXplOiBMb29rIGF0IHRoZSBlc3RpbWF0ZWQgbm9uLWxpbmVhciBmdW5jdGlvbnMKCmdhbV9tb2QxICU+JSBwbHVjaygnZml0JykgJT4lIHBsb3QoKQpgYGAKCiMjIyBFdmFsdWF0aW9uIE1ldHJpY3MKCmBgYHtyfQpnYW0xX291dHB1dCA8LSBnYW1fbW9kMSU+JSAKICAgIHByZWRpY3QobmV3X2RhdGEgPSBpbWRiX2NsZWFuKSAlPiUKICAgIGJpbmRfY29scyhpbWRiX2NsZWFuKSAlPiUKICAgIG11dGF0ZShyZXNpZCA9IEdyb3NzIC0gLnByZWQpCgpnYW0xX291dHB1dCAlPiUKICAgIHJtc2UodHJ1dGggPSBHcm9zcywgZXN0aW1hdGUgPSAucHJlZCkKCmdhbTFfb3V0cHV0ICU+JQogICAgcnNxKHRydXRoID0gR3Jvc3MsIGVzdGltYXRlID0gLnByZWQpCmBgYAoKIyMgR0FNIHdpdGggU3BsaW5lcyAoUmVjaXBlKQoKIyMjIEJ1aWxkIHRoZSBHQU0KCmBgYHtyfQpzcGxpbmVfcmVjIDwtIHJlY2lwZShHcm9zcyB+IFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyAKICAgICAgICAgICAgICAgICAgIE5vX29mX1ZvdGVzICsgR2VucmVfMSwgZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lIAogICAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgCiAgICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lCiAgICBzdGVwX25hb21pdChHcm9zcykgJT4lCiAgICBzdGVwX25zKFJ1bnRpbWUsIGRlZ19mcmVlID0gMikgJT4lCiAgICBzdGVwX25zKE5vX29mX1ZvdGVzLCBkZWdfZnJlZSA9IDYpICU+JQogICAgc3RlcF9ucyhJTURCX1JhdGluZywgZGVnX2ZyZWUgPSAyKQoKCnNwbGluZV9yZWMgJT4lIHByZXAoaW1kYl9jbGVhbikgJT4lIGp1aWNlKCkKYGBgCgpgYGB7cn0KIyBCdWlsZCB0aGUgR0FNCgpsbV9zcGVjX2dhbSA8LQogIGxpbmVhcl9yZWcoKSAlPiUKICBzZXRfZW5naW5lKGVuZ2luZSA9ICdsbScpICU+JQogIHNldF9tb2RlKCdyZWdyZXNzaW9uJykKCnNwbGluZV93ZiA8LSB3b3JrZmxvdygpICU+JQogICAgYWRkX21vZGVsKGxtX3NwZWMpICU+JQogICAgYWRkX3JlY2lwZShzcGxpbmVfcmVjKQoKY3Zfb3V0cHV0X3NwbGluZTIgPC0gZml0X3Jlc2FtcGxlcyggCiAgc3BsaW5lX3dmLCAjIHdvcmtmbG93CiAgcmVzYW1wbGVzID0gZGF0YV9jdjEwLCAjIGN2IGZvbGRzCiAgbWV0cmljcyA9IG1ldHJpY19zZXQobWFlLHJtc2UscnNxKQopCgpgYGAKCiMjIyBFdmFsdWF0aW9uIE1ldHJpY3MKCmBgYHtyfQpjdl9vdXRwdXRfc3BsaW5lMiAlPiUgY29sbGVjdF9tZXRyaWNzKCkKCmBgYAoKIyMjIGlkayB3aGF0IHRoaXMgaXMgZm9yCgpgYGB7cn0KbmV3X21vZCA8LSBzcGxpbmVfd2YgJT4lCiAgZml0KGRhdGEgPSBpbWRiX2NsZWFuKQoKbmV3X21vZCAlPiUKICB0aWR5KCkgCgpuZXdfbW9kICU+JSAKICB0aWR5KCkgJT4lIAogIGFycmFuZ2UoZGVzYyhhYnMoc3RhdGlzdGljKSkpCgpuZXdfbW9kY3YgPC0gZml0X3Jlc2FtcGxlcyhzcGxpbmVfd2YsIHJlc2FtcGxlcyA9IGRhdGFfY3YxMCwgbWV0cmljcyA9IG1ldHJpY19zZXQobWFlLCBybXNlLCByc3EpKQpgYGAKCiMjIEdBTSB3aXRoIFNwbGluZXMgYW5kIExBU1NPCgojIyMgQnVpbGQgYW5kIFR1bmUgTW9kZWwKCmBgYHtyfQojIExhc3NvIE1vZGVsIFNwZWMgd2l0aCB0dW5lCmdhbV9sYXNzb19zcGVjX3R1bmUgPC0gCiAgbGluZWFyX3JlZygpICU+JQogIHNldF9hcmdzKG1peHR1cmUgPSAxLCBwZW5hbHR5ID0gdHVuZSgpKSAlPiUgIyMgbWl4dHVyZSA9IDEgaW5kaWNhdGVzIExhc3NvCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAnZ2xtbmV0JykgJT4lIAogIHNldF9tb2RlKCdyZWdyZXNzaW9uJykgCgojIFJlY2lwZSB3aXRoIHN0YW5kYXJkaXphdGlvbiAoISkgLS0+IGp1c3QgaW5jbHVkZSBhbGwgdGhlc2UgYWx3YXlzCmRhdGFfcmVjIDwtIHJlY2lwZShHcm9zcyB+IFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyAKICAgICAgICAgICAgICAgICAgIE5vX29mX1ZvdGVzICsgR2VucmVfMSwgZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lICMgcmVtb3ZlcyB2YXJpYWJsZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZSAoc28gZHVwbGljYXRlcyBkb24ndCBtZXNzIHVwIG1vZGVsKQogICAgc3RlcF9ub3ZlbChhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSAjIGltcG9ydGFudCBpZiB5b3UgaGF2ZSByYXJlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAKICAgIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lICAjIHN1cGVyIGltcG9ydGFudCBzdGFuZGFyZGl6YXRpb24gc3RlcCBmb3IgTEFTU08KICAgIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUgICMgY3JlYXRlcyBpbmRpY2F0b3IgdmFyaWFibGVzIGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKICAgIHN0ZXBfbmFvbWl0KEdyb3NzKQoKIyBXb3JrZmxvdyAoUmVjaXBlICsgTW9kZWwpCmxhc3NvX3dmX3R1bmUxIDwtIHdvcmtmbG93KCkgJT4lIAogIGFkZF9yZWNpcGUoZGF0YV9yZWMpICU+JQogIGFkZF9tb2RlbChnYW1fbGFzc29fc3BlY190dW5lKSAKCiMgVHVuZSBNb2RlbCAodHJ5aW5nIGEgdmFyaWV0eSBvZiB2YWx1ZXMgb2YgTGFtYmRhIHBlbmFsdHkpCnBlbmFsdHlfZ3JpZCA8LSBncmlkX3JlZ3VsYXIoCiAgcGVuYWx0eShyYW5nZSA9IGMoLTMsIDEpKSwKICBsZXZlbHMgPSAzMCkKCnR1bmVfcmVzMSA8LSB0dW5lX2dyaWQoICMgbmV3IGZ1bmN0aW9uIGZvciB0dW5pbmcgcGFyYW1ldGVycwogIGxhc3NvX3dmX3R1bmUxLCAjIHdvcmtmbG93CiAgcmVzYW1wbGVzID0gZGF0YV9jdjEwLCAjIGN2IGZvbGRzCiAgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSwgbWFlKSwKICBncmlkID0gcGVuYWx0eV9ncmlkICMgcGVuYWx0eSBncmlkIGRlZmluZWQgYWJvdmUKKQoKIyBWaXN1YWxpemUgTW9kZWwgRXZhbHVhdGlvbiBNZXRyaWNzIGZyb20gVHVuaW5nCmF1dG9wbG90KHR1bmVfcmVzMSkgKyB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyMgQ29sbGVjdCBDViBNZXRyaWNzIGFuZCBTZWxlY3QgQmVzdCBNb2RlbAoKYGBge3J9CgojIFN1bW1hcml6ZSBNb2RlbCBFdmFsdWF0aW9uIE1ldHJpY3MgKENWKQpjb2xsZWN0X21ldHJpY3ModHVuZV9yZXMpICU+JQogIGZpbHRlcigubWV0cmljID09ICdybXNlJykgJT4lICMgb3IgY2hvb3NlIG1hZQogIHNlbGVjdChwZW5hbHR5LCBybXNlID0gbWVhbikgCgpiZXN0X3BlbmFsdHkxIDwtIHNlbGVjdF9iZXN0KHR1bmVfcmVzMSwgbWV0cmljID0gJ3Jtc2UnKSAjIGNob29zZSBwZW5hbHR5IHZhbHVlIGJhc2VkIG9uIGxvd2VzdCBtYWUgb3Igcm1zZQpgYGAKCiMjIyBGaXQgRmluYWwgR0FNIExBU1NPIE1vZGVsCgpgYGB7cn0KIyBGaXQgRmluYWwgTW9kZWwKCmZpbmFsX3dmX2dhbV9sYXNzbyA8LSBmaW5hbGl6ZV93b3JrZmxvdyhsYXNzb193Zl90dW5lMSwgYmVzdF9wZW5hbHR5KSAjIGluY29ycG9yYXRlcyBwZW5hbHR5IHZhbHVlIHRvIHdvcmtmbG93CgpmaW5hbF9maXRfZ2FtX2xhc3NvIDwtIGZpdChmaW5hbF93Zl9nYW1fbGFzc28sIGRhdGEgPSBpbWRiX2NsZWFuKQoKdGlkeShmaW5hbF9maXRfZ2FtX2xhc3NvKQpgYGAKCmBgYHtyfQojIEZpbmFsICgiYmVzdCIpIG1vZGVsIHByZWRpY3RvcnMgYW5kIGNvZWZmaWNpZW50cwoKZmluYWxfZml0X2dhbV9sYXNzbyAlPiUgdGlkeSgpICU+JSBmaWx0ZXIoZXN0aW1hdGUgIT0gMCkKYGBgCgojIyMgRXZhbHVhdGlvbiBNZXRyaWNzCgpgYGB7cn0KZ2FtX2xhc3NvX291dHB1dCA8LSBmaXRfcmVzYW1wbGVzKCAKICBmaW5hbF93Zl9nYW1fbGFzc28sICMgd29ya2Zsb3cKICByZXNhbXBsZXMgPSBkYXRhX2N2MTAsICMgY3YgZm9sZHMKICBtZXRyaWNzID0gbWV0cmljX3NldChtYWUscm1zZSxyc3EpCikKCmdhbV9sYXNzb19vdXRwdXQgJT4lIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKIyMgQ29tcGFyZSBHQU0gTW9kZWxzCgpgYGB7cn0KZ2FtMV9vdXRwdXQgJT4lCiAgICBybXNlKHRydXRoID0gR3Jvc3MsIGVzdGltYXRlID0gLnByZWQpCgpjdl9vdXRwdXRfc3BsaW5lMiAlPiUgY29sbGVjdF9tZXRyaWNzKCkKCmdhbV9sYXNzb19vdXRwdXQgJT4lIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKCgojIFZpc3VhbGl6ZSBSZXNpZHVhbHMKCmBgYHtyfQoKIyBWaXN1YWxpemUgUmVzaWR1YWxzCmdhbTFfb3V0cHV0IDwtIG5ld19tb2QgJT4lCiAgICBwcmVkaWN0KG5ld19kYXRhID0gaW1kYl9jbGVhbikgJT4lCiAgICBiaW5kX2NvbHMoaW1kYl9jbGVhbikgJT4lCiAgICBtdXRhdGUocmVzaWQgPSBHcm9zcyAtIC5wcmVkKQoKCmdncGxvdChnYW0xX291dHB1dCwgYWVzKHggPSAucHJlZCwgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGdhbTFfb3V0cHV0LCBhZXMoeCA9IFJ1bnRpbWUsIHkgPSByZXNpZCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChnYW0xX291dHB1dCwgYWVzKHggPSBJTURCX1JhdGluZywgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGdhbTFfb3V0cHV0LCBhZXMoeCA9IE5vX29mX1ZvdGVzLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoKCiMjIENvbXBhcmUgQWxsIExpbmVhciBSZWdyZXNzaW9uIE1vZGVsIFBlcmZvcm1hbmNlCgpgYGB7cn0KZnVsbF9sbV9tb2RlbGN2ICU+JSBjb2xsZWN0X21ldHJpY3MoKSAjT0xTCmBgYAoKYGBge3J9Cmxhc3NvX2ZpdCAlPiUgY29sbGVjdF9tZXRyaWNzKCkgI0xBU1NPCmBgYAoKYGBge3J9CmdhbTFfb3V0cHV0ICU+JQogICAgcm1zZSh0cnV0aCA9IEdyb3NzLCBlc3RpbWF0ZSA9IC5wcmVkKSAjR0FNCmBgYAoKIyBCYWdnaW5nIGFuZCBSYW5kb20gRm9yZXN0cwoKYGBge3J9CmltZGJfY2xlYW5fY2xhc3MgPC0gaW1kYl9jbGVhbiAlPiUKICBtdXRhdGUoc3VjY2Vzc19yYXRpbyA9IFJldmVudWUvQnVkZ2V0KSAlPiUKICBtdXRhdGUoZmxvcCA9IGFzLmZhY3RvcihpZmVsc2Uoc3VjY2Vzc19yYXRpbyA+IDIsICdGQUxTRScsICdUUlVFJykpKSAlPiUKICBkcm9wX25hKGZsb3AsIE5vX29mX1ZvdGVzLFJ1bnRpbWUsIElNREJfUmF0aW5nLE1ldGFfc2NvcmUsR2VucmVfMSkKYGBgCgpgYGB7cn0KIyBNb2RlbCBTcGVjaWZpY2F0aW9uCnJmX3NwZWMgPC0gcmFuZF9mb3Jlc3QoKSAlPiUKICBzZXRfZW5naW5lKGVuZ2luZSA9ICdyYW5nZXInKSAlPiUgCiAgc2V0X2FyZ3ModHJlZXMgPSAxMDAwLCAjIE51bWJlciBvZiB0cmVlcwogICAgICAgICAgIG1pbl9uID0gTlVMTCwKICAgICAgICAgICBwcm9iYWJpbGl0eSA9IEZBTFNFLAogICAgICAgICAgIGltcG9ydGFuY2UgPSAnaW1wdXJpdHknKSAlPiUKICBzZXRfbW9kZSgnY2xhc3NpZmljYXRpb24nKQoKIyBSZWNpcGUKZGF0YV9yZWMgPC0gcmVjaXBlKGZsb3AgfiBOb19vZl9Wb3RlcyArIFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyBHZW5yZV8xLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpICU+JQogIHN0ZXBfbmFvbWl0KGZsb3AsIE5vX29mX1ZvdGVzLCBSdW50aW1lLCBJTURCX1JhdGluZywgTWV0YV9zY29yZSwgR2VucmVfMSkKCiMgQ3JlYXRlIFdvcmtmbG93cwpkYXRhX3dmX210cnkzIDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSAzKSkgJT4lCiAgYWRkX3JlY2lwZShkYXRhX3JlYykgCgpkYXRhX3dmX210cnk0IDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSA0KSkgJT4lCiAgYWRkX3JlY2lwZShkYXRhX3JlYykgCgpkYXRhX3dmX210cnk1IDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSA1KSkgJT4lCiAgYWRkX3JlY2lwZShkYXRhX3JlYykKYGBgCgpgYGB7cn0KIyBGaXQgTW9kZWxzCnNldC5zZWVkKDEyMykgIyBtYWtlIHN1cmUgdG8gcnVuIHRoaXMgYmVmb3JlIGVhY2ggZml0IHNvIHRoYXQgeW91IGhhdmUgdGhlIHNhbWUgMTAwMCB0cmVlcwpkYXRhX2ZpdF9tdHJ5MyA8LSBmaXQoZGF0YV93Zl9tdHJ5MywgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpCgpzZXQuc2VlZCgxMjMpIApkYXRhX2ZpdF9tdHJ5NCA8LSBmaXQoZGF0YV93Zl9tdHJ5NCwgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpCgpzZXQuc2VlZCgxMjMpCmRhdGFfZml0X210cnk1IDwtIGZpdChkYXRhX3dmX210cnk1LCBkYXRhID0gaW1kYl9jbGVhbl9jbGFzcykKYGBgCgpgYGB7cn0KIyBDdXN0b20gRnVuY3Rpb24gdG8gZ2V0IE9PQiBwcmVkaWN0aW9ucywgdHJ1ZSBvYnNlcnZlZCBvdXRjb21lcyBhbmQgYWRkIGEgdXNlci1wcm92aWRlZCBtb2RlbCBsYWJlbApyZl9PT0Jfb3V0cHV0IDwtIGZ1bmN0aW9uKGZpdF9tb2RlbCwgbW9kZWxfbGFiZWwsIHRydXRoKXsKICAgIHRpYmJsZSgKICAgICAgICAgIC5wcmVkX2NsYXNzID0gZml0X21vZGVsICU+JSBleHRyYWN0X2ZpdF9lbmdpbmUoKSAlPiUgcGx1Y2soJ3ByZWRpY3Rpb25zJyksICNPT0IgcHJlZGljdGlvbnMKICAgICAgICAgIGZsb3AgPSB0cnV0aCwKICAgICAgICAgIG1vZGVsID0gbW9kZWxfbGFiZWwKICAgICAgKQp9CmBgYAoKCmBgYHtyfQojIEV2YWx1YXRlIE9PQiBNZXRyaWNzCgpkYXRhX3JmX09PQl9vdXRwdXQgPC0gYmluZF9yb3dzKAogICAgcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5MywzLCBpbWRiX2NsZWFuX2NsYXNzICU+JSBwdWxsKGZsb3ApKSwKICAgIHJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTQsNCwgaW1kYl9jbGVhbl9jbGFzcyAlPiUgcHVsbChmbG9wKSksCiAgICByZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnk1LDUsIGltZGJfY2xlYW5fY2xhc3MgJT4lIHB1bGwoZmxvcCkpCikKCgpkYXRhX3JmX09PQl9vdXRwdXQgJT4lIAogICAgZ3JvdXBfYnkobW9kZWwpICU+JQogICAgYWNjdXJhY3kodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQoKZGF0YV9yZl9PT0Jfb3V0cHV0ICU+JSAKICBncm91cF9ieShtb2RlbCkgJT4lCiAgYWNjdXJhY3kodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSAlPiUKICBtdXRhdGUobXRyeSA9IGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3JlcGxhY2UobW9kZWwsJ210cnknLCcnKSkpICU+JQogIGdncGxvdChhZXMoeCA9IG10cnksIHkgPSAuZXN0aW1hdGUgKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyMgRXZhbHVhdGluZyB0aGUgRm9yZXN0CgpgYGB7cn0KcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5NCw0LCBpbWRiX2NsZWFuX2NsYXNzICU+JSBwdWxsKGZsb3ApKSAlPiUKICAgIGNvbmZfbWF0KHRydXRoID0gZmxvcCwgZXN0aW1hdGU9IC5wcmVkX2NsYXNzKQpgYGAKCiMjIyBWYXJpYWJsZSBJbXBvcnRhbmNlCgpgYGB7cn0KIyBJbXB1cml0eQoKbW9kZWxfb3V0cHV0IDwtZGF0YV9maXRfbXRyeTQgJT4lIAogICAgZXh0cmFjdF9maXRfZW5naW5lKCkgCgptb2RlbF9vdXRwdXQgJT4lIAogICAgdmlwKG51bV9mZWF0dXJlcyA9IDEwKSArIHRoZW1lX2NsYXNzaWMoKSAjYmFzZWQgb24gaW1wdXJpdHksIDEwIG1lYW5pbmcgdGhlIHRvcCAxMAoKbW9kZWxfb3V0cHV0ICU+JSB2aXA6OnZpKCkgJT4lIGhlYWQoKQptb2RlbF9vdXRwdXQgJT4lIHZpcDo6dmkoKSAlPiUgdGFpbCgpCmBgYAoKYGBge3J9CiMgUGVybXV0YXRpb24KCm1vZGVsX291dHB1dDIgPC0gZGF0YV93Zl9tdHJ5NCAlPiUgCiAgdXBkYXRlX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKGltcG9ydGFuY2UgPSAicGVybXV0YXRpb24iKSkgJT4lICNiYXNlZCBvbiBwZXJtdXRhdGlvbgogIGZpdChkYXRhID0gaW1kYl9jbGVhbl9jbGFzcykgJT4lIAogICAgZXh0cmFjdF9maXRfZW5naW5lKCkgCgptb2RlbF9vdXRwdXQyICU+JSAKICAgIHZpcChudW1fZmVhdHVyZXMgPSAxMCkgKyB0aGVtZV9jbGFzc2ljKCkKCgptb2RlbF9vdXRwdXQyICU+JSB2aXA6OnZpKCkgJT4lIGhlYWQoKQptb2RlbF9vdXRwdXQyICU+JSB2aXA6OnZpKCkgJT4lIHRhaWwoKQpgYGAKYGBge3J9CmdncGxvdChpbWRiX2NsZWFuX2NsYXNzLCBhZXMoeCA9IGZsb3AsIHkgPSBOb19vZl9Wb3RlcykpICsKICAgIGdlb21fdmlvbGluKCkgKyB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChpbWRiX2NsZWFuX2NsYXNzLCBhZXMoeCA9IGZsb3AsIHkgPSBSdW50aW1lKSkgKwogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGltZGJfY2xlYW5fY2xhc3MsIGFlcyh4ID0gZmxvcCwgeSA9IElNREJfUmF0aW5nKSkgKwogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGltZGJfY2xlYW5fY2xhc3MsIGFlcyh4ID0gZmxvcCwgeSA9IE1ldGFfc2NvcmUpKSArCiAgICBnZW9tX3Zpb2xpbigpICsgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyBMb2dpc3RpYyBSZWdyZXNzaW9uCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQoKIyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIFNwZWMKbG9naXN0aWNfc3BlYyA8LSBsb2dpc3RpY19yZWcoKSAlPiUKICAgIHNldF9lbmdpbmUoJ2dsbScpICU+JQogICAgc2V0X21vZGUoJ2NsYXNzaWZpY2F0aW9uJykKCiMgUmVjaXBlCmxvZ2lzdGljX3JlYyA8LSByZWNpcGUoZmxvcCB+IE5vX29mX1ZvdGVzICsgUnVudGltZSArIElNREJfUmF0aW5nICsgR2VucmVfMSwKICAgICAgICAgICAgICAgICAgIGRhdGEgPSBpbWRiX2NsZWFuX2NsYXNzKQoKIyBXb3JrZmxvdyAoUmVjaXBlICsgTW9kZWwpIGZvciBGdWxsIExvZyBNb2RlbApsb2dfd2YgPC0gd29ya2Zsb3coKSAlPiUKICAgIGFkZF9yZWNpcGUobG9naXN0aWNfcmVjKSAlPiUKICAgIGFkZF9tb2RlbChsb2dpc3RpY19zcGVjKQoKIyBGaXQgTW9kZWwKbG9nX2ZpdCA8LSBmaXQobG9nX3dmLCBkYXRhID0gaW1kYl9jbGVhbl9jbGFzcykKCnRpZHkobG9nX2ZpdCkKYGBgCgoKYGBge3J9CmxvZ19maXQgJT4lIHRpZHkoKSAlPiUKICBtdXRhdGUoT1IgPSBleHAoZXN0aW1hdGUpKQpgYGAKCipBc2sgQnJ5YW4gYWJvdXQgcmVsZXZlbCBmYWN0b3IuLi4gZG8gd2UgbmVlZCBpdD8qCgpgYGB7cn0KIyBDcmVhdGlvbiBvZiBDViBGb2xkcwpkYXRhX2N2MTBfY2xhc3MgPC0gdmZvbGRfY3YoaW1kYl9jbGVhbl9jbGFzcywgdiA9IDEwKQpgYGAKCgojIFJlY2lwZXMgYW5kIFdvcmtmbG93cwoKYGBge3J9CmZ1bGxfbG9nX3dmIDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShsb2dpc3RpY19yZWMpICU+JQogIGFkZF9tb2RlbChsb2dpc3RpY19zcGVjKQogICAgCmxvZ19tb2RlbCA8LSBmaXQoZnVsbF9sb2dfd2YsIGRhdGEgPSBpbWRiX2NsZWFuX2NsYXNzKSAKCmxvZ19tb2RlbGN2IDwtIGZpdF9yZXNhbXBsZXMoZnVsbF9sb2dfd2YsIHJlc2FtcGxlcyA9IGRhdGFfY3YxMF9jbGFzcywgbWV0cmljcyA9IG1ldHJpY19zZXQoYWNjdXJhY3ksc2Vucyx5YXJkc3RpY2s6OnNwZWMpKQoKbG9nX21vZGVsY3YgJT4lCiAgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgpgYGB7cn0KIyBTb2Z0IFByZWRpY3Rpb25zIG9uIFRyYWluaW5nIERhdGEKZmluYWxfb3V0cHV0IDwtIGxvZ19maXQgJT4lIHByZWRpY3QobmV3X2RhdGEgPSBpbWRiX2NsZWFuX2NsYXNzLCB0eXBlPSdwcm9iJykgJT4lIGJpbmRfY29scyhpbWRiX2NsZWFuX2NsYXNzKQoKZmluYWxfb3V0cHV0ICU+JQogIGdncGxvdChhZXMoeCA9IGZsb3AsIHkgPSAucHJlZF9UUlVFKSkgKwogIGdlb21fYm94cGxvdCgpCmBgYAoKYGBge3J9CiMgVXNlIHNvZnQgcHJlZGljdGlvbnMKZmluYWxfb3V0cHV0ICU+JQogICAgcm9jX2N1cnZlKGZsb3AsLnByZWRfVFJVRSxldmVudF9sZXZlbCA9ICdzZWNvbmQnKSAlPiUKICAgIGF1dG9wbG90KCkKYGBgCgpgYGB7cn0KIyB0aHJlc2hvbGRzIGluIHRlcm1zIG9mIHJlZmVyZW5jZSBsZXZlbAp0aHJlc2hvbGRfb3V0cHV0IDwtIGZpbmFsX291dHB1dCAlPiUKICAgIHRocmVzaG9sZF9wZXJmKHRydXRoID0gZmxvcCwgZXN0aW1hdGUgPSAucHJlZF9GQUxTRSwgdGhyZXNob2xkcyA9IHNlcSgwLDEsYnk9LjAxKSkgCgojIEotaW5kZXggdi4gdGhyZXNob2xkIGZvciBubyBmbG9wCnRocmVzaG9sZF9vdXRwdXQgJT4lCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnal9pbmRleCcpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gLnRocmVzaG9sZCwgeSA9IC5lc3RpbWF0ZSkpICsKICAgIGdlb21fbGluZSgpICsKICAgIGxhYnMoeSA9ICdKLWluZGV4JywgeCA9ICd0aHJlc2hvbGQnKSArCiAgICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0KdGhyZXNob2xkX291dHB1dCAlPiUKICAgIGZpbHRlcigubWV0cmljID09ICdqX2luZGV4JykgJT4lCiAgICBhcnJhbmdlKGRlc2MoLmVzdGltYXRlKSkKYGBgCgpgYGB7cn0KIyBEaXN0YW5jZSB2LiB0aHJlc2hvbGQgZm9yIG5vdF9zcGFtCgp0aHJlc2hvbGRfb3V0cHV0ICU+JQogICAgZmlsdGVyKC5tZXRyaWMgPT0gJ2Rpc3RhbmNlJykgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSAudGhyZXNob2xkLCB5ID0gLmVzdGltYXRlKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgbGFicyh5ID0gJ0Rpc3RhbmNlJywgeCA9ICd0aHJlc2hvbGQnKSArCiAgICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0KdGhyZXNob2xkX291dHB1dCAlPiUKICAgIGZpbHRlcigubWV0cmljID09ICdkaXN0YW5jZScpICU+JQogICAgYXJyYW5nZSguZXN0aW1hdGUpCmBgYAoKYGBge3J9CmxvZ19tZXRyaWNzIDwtIG1ldHJpY19zZXQoYWNjdXJhY3ksc2Vucyx5YXJkc3RpY2s6OnNwZWMpCgpmaW5hbF9vdXRwdXQgJT4lCiAgICBtdXRhdGUoLnByZWRfY2xhc3MgPSBtYWtlX3R3b19jbGFzc19wcmVkKC5wcmVkX0ZBTFNFLCBsZXZlbHMoZmxvcCksIHRocmVzaG9sZCA9IC4xMikpICU+JQogICAgbG9nX21ldHJpY3ModHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzLCBldmVudF9sZXZlbCA9ICdzZWNvbmQnKQoKZmluYWxfb3V0cHV0ICU+JQogIG11dGF0ZSgucHJlZF9jbGFzcyA9IG1ha2VfdHdvX2NsYXNzX3ByZWQoLnByZWRfRkFMU0UsIGxldmVscyhmbG9wKSwgdGhyZXNob2xkID0gLjEyKSkgJT4lCiAgY29uZl9tYXQodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQpgYGAKCmBgYHtyfQpwcmVkaWN0KGxvZ19maXQsIG5ld19kYXRhID0gZGF0YS5mcmFtZShOb19vZl9Wb3RlcyA9IDEwMDAwLCBSdW50aW1lID0gMTEyLCBJTURCX1JhdGluZyA9IDkuOCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdlbnJlXzEgPSAiRHJhbWEiKSwgdHlwZSA9ICJwcm9iIgopCmBgYAoKCmBgYHtyfQpwcmVkaWN0KGxvZ19maXQsIG5ld19kYXRhID0gZGF0YS5mcmFtZShOb19vZl9Wb3RlcyA9IDEwMDAwLCBSdW50aW1lID0gMTEyLCBJTURCX1JhdGluZyA9IDkuOCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdlbnJlXzEgPSAiRHJhbWEiKSwgdHlwZSA9ICJjbGFzcyIKKQpgYGAKCipNdXN0IG1hbnVhbGx5IGNhbGN1bGF0ZSBoYXJkIHByZWRpY3Rpb24qCgojIyMgSy1NZWFucyBDbHVzdGVyaW5nCgpgYGB7cn0KZ2dwbG90KGltZGJfY2xlYW4sIGFlcyh4ID0gQnVkZ2V0LCB5ID0gUnVudGltZSkpICsgCiAgZ2VvbV9wb2ludCgpICsgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoaW1kYl9jbGVhbiwgYWVzKHggPSBCdWRnZXQsIHkgPSBHcm9zcykpICsgCiAgZ2VvbV9wb2ludCgpICsgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoaW1kYl9jbGVhbiwgYWVzKHggPSBOb19vZl9Wb3RlcywgeSA9IFJ1bnRpbWUpKSArIAogIGdlb21fcG9pbnQoKSArIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGltZGJfY2xlYW4sIGFlcyh4ID0gR3Jvc3MsIHkgPSBOb19vZl9Wb3RlcykpICsgCiAgZ2VvbV9wb2ludCgpICsgdGhlbWVfY2xhc3NpYygpCmBgYAoKCmBgYHtyfQojIFNlbGVjdCBqdXN0IHRoZSBiaWxsIGxlbmd0aCBhbmQgZGVwdGggdmFyaWFibGVzCmltZGJfc3ViIDwtIGltZGJfY2xlYW4gJT4lCiAgICBzZWxlY3QoQnVkZ2V0LCBSdW50aW1lKQoKIyBSdW4gay1tZWFucyBmb3IgayA9IGNlbnRlcnMgPSAzCnNldC5zZWVkKDI1MykKa2NsdXN0X2sxMCA8LSBrbWVhbnMoc2NhbGUoaW1kYl9zdWIpLCBjZW50ZXJzID0gMTApCgojIERpc3BsYXkgdGhlIGNsdXRlciBhc3NpZ25tZW50cwprY2x1c3RfazEwJGNsdXN0ZXIKCiMgQWRkIGEgdmFyaWFibGUgKGtjbHVzdF8zKSB0byB0aGUgb3JpZ2luYWwgZGF0YXNldCAKIyBjb250YWluaW5nIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzCmltZGJfY2xlYW4gPC0gaW1kYl9jbGVhbiAlPiUKICAgIG11dGF0ZShrY2x1c3RfMTAgPSBmYWN0b3Ioa2NsdXN0X2sxMCRjbHVzdGVyKSkKYGBgCgpgYGB7cn0KIyBWaXN1YWxpemUgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMgb24gdGhlIG9yaWdpbmFsIHNjYXR0ZXJwbG90CmltZGJfY2xlYW4gJT4lCiAgZ2dwbG90KGFlcyh4ID0gQnVkZ2V0LCB5ID0gUnVudGltZSwgY29sb3IgPSBrY2x1c3RfMTApKSArCiAgICBnZW9tX3BvaW50KCkgKyB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0KIyBEYXRhLXNwZWNpZmljIGZ1bmN0aW9uIHRvIGNsdXN0ZXIgYW5kIGNhbGN1bGF0ZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBTUwppbWRiX2NsdXN0ZXJfc3MgPC0gZnVuY3Rpb24oayl7CiAgICAjIFBlcmZvcm0gY2x1c3RlcmluZwogICAga2NsdXN0IDwtIGttZWFucyhzY2FsZShpbWRiX3N1YiksIGNlbnRlcnMgPSBrKQoKICAgICMgUmV0dXJuIHRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlcwogICAgcmV0dXJuKGtjbHVzdCR0b3Qud2l0aGluc3MpCn0KCnRpYmJsZSgKICAgIGsgPSAxOjE1LAogICAgdG90X3djX3NzID0gcHVycnI6Om1hcF9kYmwoMToxNSwgaW1kYl9jbHVzdGVyX3NzKQopICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGssIHkgPSB0b3Rfd2Nfc3MpKSArCiAgICBnZW9tX3BvaW50KCkgKyAKICAgIGxhYnMoeCA9ICJOdW1iZXIgb2YgY2x1c3RlcnMiLHkgPSAnVG90YWwgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxdWFyZXMnKSArIAogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CmtjbHVzdF9rOCA8LSBrbWVhbnMoc2NhbGUoaW1kYl9zdWIpLCBjZW50ZXJzID0gOCkKCiMgRGlzcGxheSB0aGUgY2x1dGVyIGFzc2lnbm1lbnRzCmtjbHVzdF9rOCRjbHVzdGVyCgojIEFkZCBhIHZhcmlhYmxlIChrY2x1c3RfMykgdG8gdGhlIG9yaWdpbmFsIGRhdGFzZXQgCiMgY29udGFpbmluZyB0aGUgY2x1c3RlciBhc3NpZ25tZW50cwppbWRiX2NsZWFuIDwtIGltZGJfY2xlYW4gJT4lCiAgICBtdXRhdGUoa2NsdXN0XzggPSBmYWN0b3Ioa2NsdXN0X2s4JGNsdXN0ZXIpKQpgYGAKCmBgYHtyfQojIFZpc3VhbGl6ZSB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBvbiB0aGUgb3JpZ2luYWwgc2NhdHRlcnBsb3QKaW1kYl9jbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBCdWRnZXQsIHkgPSBSdW50aW1lLCBjb2xvciA9IGtjbHVzdF84KSkgKwogICAgZ2VvbV9wb2ludCgpICsgdGhlbWVfY2xhc3NpYygpCmBgYAoKKlBpY2sgZWl0aGVyIGsgPSA4IG9yIGsgPSAxMCAtLT4gdGhlcmUgYXJlIDcgZ2VucmVzIHdpdGggbGFyZ2VyIG4gYW5kIDEzIHRvdGFsIGdlbnJlcy4gRmlsbSBub2lyIGlzIGZhbmN5IHdheSBvZiBzYXlpbmcgY3JpbWUgZHJhbWEgKGV4LiBLbml2ZXMgT3V0KS4gT25lIHRoaW5nIHRvIG5vdGUgaXMgYSBzaWduaWZpY2FudCBudW1iZXIgb2YgbW92aWVzIGhhdmUgR2VucmVfMiBsaXN0ZWQgYXMgRmFtaWx5IE15c3RlcnkgYW5kIEhvcnJvciAoYW5kIG1hbnkgaGF2ZSBvbmUgb2YgdGhlIG1haW4gZ2VucmVzIGxpc3RlZCBhcyBnZW5yZSAyIGFzIHdlbGwpLiBXaGF0IGlzIGEgZ29vZCB3YXkgdG8gc3VtbWFyaXplIGJvdGggR2VucmUgMSBhbmQgMj8qCgpgYGB7cn0KaW1kYl9jbGVhbiAlPiUKICBjb3VudChHZW5yZV8xKQoKaW1kYl9jbGVhbiAlPiUKICBjb3VudChHZW5yZV8yKQpgYGAKCiMjIEludGVycHJldGluZyB0aGUgQ2x1c3RlcnMKCmBgYHtyfQppbWRiX2NsZWFuICU+JQogIGdyb3VwX2J5KGtjbHVzdF8xMCkgJT4lCiAgc3VtbWFyaXplKGFjcm9zcyhjKEdyb3NzLCBCdWRnZXQpLCBtZWFuKSkKCmltZGJfY2xlYW4gJT4lCiAgZ3JvdXBfYnkoa2NsdXN0XzgpICU+JQogIGNvdW50KEdlbnJlXzEpCgppbWRiX2NsZWFuICU+JQogIGdyb3VwX2J5KGtjbHVzdF84KSAlPiUKICBjb3VudChHZW5yZV8yKQoKaW1kYl9jbGVhbiU+JQogIGNvdW50KGtjbHVzdF84KQpgYGAKCgoKIyMjIEhpZXJhcmNoaWFsIENsdXN0ZXJpbmcKCmBgYHtyfQojIFJhbmRvbSBzdWJzYW1wbGUgb2YgNTAgcGVuZ3VpbnMKc2V0LnNlZWQoMjUzKQppbWRiX2hjIDwtIGltZGJfY2xlYW4gJT4lCiAgICBzbGljZV9zYW1wbGUobiA9IDI1KQoKIyBTZWxlY3QgdGhlIHZhcmlhYmxlcyB0byBiZSB1c2VkIGluIGNsdXN0ZXJpbmcKaW1kYl9oY19zdWIgPC0gaW1kYl9oYyAlPiUKICAgIHNlbGVjdChHcm9zcywgQnVkZ2V0KQoKaW1kYl9oY19mdWxsIDwtIGltZGJfY2xlYW4gJT4lCiAgc2VsZWN0KEdyb3NzLCBCdWRnZXQpCgojIFN1bW1hcnkgc3RhdGlzdGljcyBmb3IgdGhlIHZhcmlhYmxlcwpzdW1tYXJ5KGltZGJfaGNfc3ViKQoKIyBDb21wdXRlIGEgZGlzdGFuY2UgbWF0cml4IG9uIHRoZSBzY2FsZWQgZGF0YQpkaXN0X21hdF9zY2FsZWQgPC0gZGlzdChzY2FsZShpbWRiX2hjX3N1YikpCgpkaXN0X21hdF9mdWxsIDwtIGRpc3Qoc2NhbGUoaW1kYl9oY19mdWxsKSkKYGBgCgpgYGB7cn0KaW1kYl9oY19hdmcgPC0gaGNsdXN0KGRpc3RfbWF0X3NjYWxlZCwgbWV0aG9kID0gImF2ZXJhZ2UiKQppbWRiX2Z1bGxfYXZnIDwtIGhjbHVzdChkaXN0X21hdF9mdWxsLCBtZXRob2QgPSAiYXZlcmFnZSIpCgojIFBsb3QgZGVuZHJvZ3JhbQpwbG90KGltZGJfaGNfYXZnKQpgYGAKCmBgYHtyfQoKcGxvdChpbWRiX2hjX2F2ZywgbGFiZWxzID0gaW1kYl9oYyRHZW5yZV8xKQoKcGxvdChpbWRiX2hjX2F2ZywgbGFiZWxzID0gcGFzdGUoaW1kYl9oYyRHZW5yZV8xLCBpbWRiX2hjJEdlbnJlXzIpKQpgYGAKCmBgYHtyfQppbWRiX2NsZWFuIDwtIGltZGJfY2xlYW4gJT4lCiAgICBtdXRhdGUoCiAgICAgICAgaGNsdXN0X251bSA9IGZhY3RvcihjdXRyZWUoaW1kYl9mdWxsX2F2ZywgayA9IDMpKSAjIEN1dCBpbnRvIDYgY2x1c3RlcnMgKGspCiAgICApCgpgYGAKCmBgYHtyfQpnZ3Bsb3QoaW1kYl9jbGVhbiwgYWVzKHggPSBoY2x1c3RfbnVtLCBmaWxsID0gR2VucmVfMSkpICsKICAgIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArCiAgICBsYWJzKHggPSAiQ2x1c3RlciIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKYGBgCgo=